﻿using System.Diagnostics.CodeAnalysis;
using NewLife.Serialization;
using NewLife.Threading;
using XCode;
using XCode.Configuration;
using XCode.DataAccessLayer;
using XCode.Extension;

namespace NewLife.Caching;

/// <summary>数据库缓存。利用数据表来缓存信息</summary>
/// <remarks>
/// 构建一个操作队列，新增、更新、删除等操作全部排队单线程执行，以改进性能
/// </remarks>
public class DbCache : NewLife.Caching.Cache
{
    #region 属性
    /// <summary>实体工厂</summary>
    protected IEntityFactory Factory { get; }

    /// <summary>主键字段</summary>
    protected Field KeyField { get; }

    /// <summary>时间字段</summary>
    protected Field TimeField { get; }
    #endregion

    #region 构造
    /// <summary>实例化一个数据库缓存</summary>
    /// <param name="factory"></param>
    /// <param name="keyName"></param>
    /// <param name="timeName"></param>
    public DbCache(IEntityFactory? factory = null, String? keyName = null, String? timeName = null)
    {
        factory ??= MyDbCache.Meta.Factory;
        if (factory.Default is not IDbCache)
            throw new XCodeException("实体类[{0}]需要实现[{1}]接口", factory.EntityType.FullName, typeof(IDbCache).FullName);

        var name = factory.EntityType.Name;

        var key = !keyName.IsNullOrEmpty() ? factory.Table.FindByName(keyName) : factory.Unique;
        if (key == null || key.Type != typeof(String)) throw new XCodeException($"[{name}]没有字符串类型的主键");

        TimeField = (!timeName.IsNullOrEmpty() ? factory.Table.FindByName(timeName) : factory.MasterTime) as Field ??
            throw new ArgumentNullException(nameof(timeName));

        Factory = factory;
        KeyField = key as Field ?? throw new ArgumentNullException(nameof(keyName));
        Name = name;

        // 关闭日志
        var db = factory.Session.Dal.Db;
        db.ShowSQL = false;
        (db as DbBase)!.TraceSQLTime *= 10;

        Init(null);
    }

    /// <summary>销毁</summary>
    /// <param name="disposing"></param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        _clearTimer.TryDispose();
        _clearTimer = null;
    }
    #endregion

    #region 属性
    /// <summary>缓存个数。高频使用时注意性能</summary>
    public override Int32 Count => Factory.Session.Count;

    /// <summary>所有键。实际返回只读列表新实例，数据量较大时注意性能</summary>
    public override ICollection<String> Keys => Factory.FindAll().Select(e => (e[Factory.Unique] as String)!).ToList();
    #endregion

    #region 方法
    /// <summary>初始化配置</summary>
    /// <param name="config"></param>
    public override void Init(String? config)
    {
        if (_clearTimer == null)
        {
            var period = 60;
            _clearTimer = new TimerX(RemoveNotAlive, null, period * 1000, period * 1000) { Async = true };
        }
    }

    private readonly MemoryCache _cache = new() { Expire = 60 };
    private IDbCache? Find(String key)
    {
        if (key.IsNullOrEmpty()) return null;

        if (_cache.TryGetValue<IDbCache>(key, out var entry)) return entry;

        entry = Factory.Find(KeyField == key) as IDbCache;

        _cache.Set(key, entry);

        return entry;
    }
    #endregion

    #region 基本操作
    /// <summary>是否包含缓存项</summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public override Boolean ContainsKey(String key) => Find(key) != null;

    /// <summary>添加缓存项，已存在时更新</summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="key">键</param>
    /// <param name="value">值</param>
    /// <param name="expire">过期时间，秒。小于0时采用默认缓存时间</param>
    /// <returns></returns>
    public override Boolean Set<T>(String key, T value, Int32 expire = -1)
    {
        if (expire < 0) expire = Expire;

        var e = Find(key);
        if (e == null)
        {
            //e = Factory.GetOrAdd(key) as IDbCache;
            e = (Factory.Create() as IDbCache)!;
            e.Name = key;
            _cache[key] = e;
        }
        e.Value = value!.ToJson();
        e.ExpiredTime = TimerX.Now.AddSeconds(expire);

        if (e.CreateTime.Year < 1000) e.CreateTime = TimerX.Now;
        e.SaveAsync();

        return true;
    }

    /// <summary>获取缓存项，不存在时返回默认值</summary>
    /// <param name="key">键</param>
    /// <returns></returns>
    [return: MaybeNull]
    public override T Get<T>(String key)
    {
        var e = Find(key);
        if (e == null) return default;

        var value = e.Value;
        //return JsonHelper.Convert<T>(value);
        //if (typeof(T) == typeof(Byte[])) return (T)(Object)(value + "").ToBase64();
        if (typeof(T) == typeof(String)) return (T)(Object)value;

        //return value.ChangeType<T>();
        return value.ToJsonEntity<T>();
    }

    /// <summary>批量移除缓存项</summary>
    /// <param name="keys">键集合</param>
    /// <returns>实际移除个数</returns>
    public override Int32 Remove(params String[] keys)
    {
        if (Count == 0) return 0;

        var count = 0;
        foreach (var item in keys)
        {
            var e = _cache.Get<Object>(item);
            if (e != null)
            {
                _cache.Remove(item);

                (e as IEntity)!.Delete();

                count++;
            }
        }
        return count;

        //var list = Factory.FindAll(KeyField.In(keys), null, null, 0, 0);
        //foreach (IDbCache item in list)
        //{
        //    _cache.Remove(item.Name);
        //}
        //return list.Delete();
    }

    /// <summary>删除所有配置项</summary>
    public override void Clear() => Factory.Session.Truncate();

    /// <summary>设置缓存项有效期</summary>
    /// <param name="key">键</param>
    /// <param name="expire">过期时间</param>
    /// <returns>设置是否成功</returns>
    public override Boolean SetExpire(String key, TimeSpan expire)
    {
        var e = Find(key);
        if (e == null) return false;

        e.ExpiredTime = TimerX.Now.Add(expire);
        e.SaveAsync();

        return true;
    }

    /// <summary>获取缓存项有效期，不存在时返回Zero</summary>
    /// <param name="key">键</param>
    /// <returns></returns>
    public override TimeSpan GetExpire(String key)
    {
        var e = Find(key);
        if (e == null) return TimeSpan.Zero;

        return e.ExpiredTime - TimerX.Now;
    }
    #endregion

    #region 高级操作
    /// <summary>添加，已存在时不更新，常用于锁争夺</summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="key">键</param>
    /// <param name="value">值</param>
    /// <param name="expire">过期时间，秒。小于0时采用默认缓存时间</param>
    /// <returns></returns>
    public override Boolean Add<T>(String key, T value, Int32 expire = -1)
    {
        if (expire < 0) expire = Expire;

        var e = Find(key);
        if (e != null) return false;

        e = (Factory.Create() as IDbCache)!;
        e.Name = key;
        e.Value = value!.ToJson();
        e.ExpiredTime = TimerX.Now.AddSeconds(expire);
        (e as IEntity)!.Insert();

        _cache[key] = e;

        return true;
    }
    #endregion

    #region 清理过期缓存
    /// <summary>清理会话计时器</summary>
    private TimerX? _clearTimer;

    /// <summary>移除过期的缓存项</summary>
    void RemoveNotAlive(Object state)
    {
        // 这里先计算，性能很重要
        var now = TimerX.Now;
        var list = Factory.FindAll(TimeField < now, null, null, 0, 0);
        foreach (IDbCache item in list)
        {
            _cache.Remove(item.Name);
        }
        list.Delete();
    }
    #endregion

    #region 性能测试
    /// <summary>使用指定线程测试指定次数</summary>
    /// <param name="times">次数</param>
    /// <param name="threads">线程</param>
    /// <param name="rand">随机读写</param>
    /// <param name="batch">批量操作</param>
    public override Int64 BenchOne(Int64 times, Int32 threads, Boolean rand, Int32 batch)
    {
        if (rand)
            times *= 1;
        else
            times *= 1000;

        return base.BenchOne(times, threads, rand, batch);
    }
    #endregion
}

/// <summary>数据缓存接口</summary>
public interface IDbCache
{
    /// <summary>名称</summary>
    String Name { get; set; }

    /// <summary>键值</summary>
    String Value { get; set; }

    /// <summary>创建时间</summary>
    DateTime CreateTime { get; set; }

    /// <summary>过期时间</summary>
    DateTime ExpiredTime { get; set; }

    /// <summary>异步保存</summary>
    /// <param name="msDelay"></param>
    /// <returns></returns>
    Boolean SaveAsync(Int32 msDelay = 0);
}